package sf.database.mapper.handle;

import sf.common.wrapper.Page;
import sf.database.dao.DBClient;
import sf.database.mapper.annotation.ExecuteTemplate;
import sf.database.mapper.annotation.OrmParam;
import sf.database.mapper.annotation.SelectKey;
import sf.database.mapper.annotation.SqlResource;
import sf.database.support.DMLType;
import sf.tools.ArrayUtils;
import sf.tools.JavaTypeUtils;
import sf.tools.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class MethodHandleTemplate {
    protected static MethodHandleTemplate ms = new MethodHandleTemplate();

    public static MethodHandleTemplate getInstance() {
        return ms;
    }

    private MethodHandleTemplate() {

    }


    public Object call(DBClient dbClient, Class entityClass, Method m, SqlResource resource, Object[] params) {
        Object ret = null;
        ExecuteTemplate et = m.getAnnotation(ExecuteTemplate.class);
        String sqlId = et.id();
        String sql = StringUtils.join(et.value());
        //是否是sqlid
        boolean isSqlId = true;

        if (StringUtils.isNotBlank(sqlId)) {
            sql = sqlId;
        } else if (StringUtils.isNotBlank(sql)) {
            isSqlId = false;
        } else {
            if (StringUtils.isBlank(sqlId)) {
                if (resource != null) {
                    String preffix = resource.value();
                    String name = m.getName();
                    sqlId = preffix + "." + name;
                } else {
                    sqlId = m.getName();
                }
            }
            sql = sqlId;
        }

        switch (et.type()) {
            case SELECT:
                ret = parseSelectList(m, dbClient, sql, params, isSqlId);
                break;
            case INSERT:
                ret = parseInsert(m, dbClient, sql, params, et.batch(), isSqlId);
                break;
            default:
                ret = parseUpdate(m, dbClient, sql, params, isSqlId);
                break;
        }
        return ret;
    }

    public Object call(DBClient dbClient, Method m, String sqlTemplate, DMLType dmlType, Object[] params) {
        Object ret = null;
        //是否是sqlid
        boolean isSqlId = false;
        boolean isBatch = false;
        switch (dmlType) {
            case SELECT:
                ret = parseSelectList(m, dbClient, sqlTemplate, params, isSqlId);
                break;
            case INSERT:
                ret = parseInsert(m, dbClient, sqlTemplate, params, isBatch, isSqlId);
                break;
            default:
                ret = parseUpdate(m, dbClient, sqlTemplate, params, isSqlId);
                break;
        }
        return ret;
    }


    protected Object parseInsert(Method method, DBClient dbClient, String sql, Object[] params, boolean batch, boolean isSqlId) {
        SelectKey returnKey = method.getAnnotation(SelectKey.class);
        List<String> pkeys = Collections.emptyList();
        if (returnKey != null && StringUtils.isNotBlank(returnKey.keyColumn())) {
            pkeys = Collections.singletonList(returnKey.keyColumn());
        }


        Map<String, Object> paraMap = new LinkedHashMap<>();
        convertParam(method, params, paraMap);

        List<Map<String, Object>> rMap = new LinkedList<>();
        List<Map<String, Object>> pa = new ArrayList<>();
        if (!batch) {
            pa.add(paraMap);
        } else {
            if (params != null && params.length > 0) {
                pa.addAll((Collection<? extends Map<String, Object>>) params[0]);
            }
        }
        if (isSqlId) {
            dbClient.executeBatchTemplate(sql, pa, false, 100, pkeys, rMap);
        } else {
            dbClient.executeBatchTemplateSource(sql, pa, false, 100, pkeys, rMap);
        }

        if (!rMap.isEmpty() && returnKey != null) {
            Object value = rMap.get(0).values().iterator().next();
            if (returnKey.resultType() == Integer.class || returnKey.resultType() == int.class) {
                return ((Number) value).intValue();
            } else if (returnKey.resultType() == Long.class || returnKey.resultType() == long.class) {
                return ((Number) value).longValue();
            } else if (returnKey.resultType() == Map.class) {
                return rMap.get(0);
            }
        }
        return null;
    }

    /**
     * 根据返回参数int 或者int[] 判断是否是批处理。如果都没有，根据第一参数判断
     * @param method
     * @param dbClient
     * @param sql
     * @param params
     * @param isSqlId
     * @return
     */
    protected Object parseUpdate(Method method, DBClient dbClient, String sql, Object[] params, boolean isSqlId) {


        Class[] paras = method.getParameterTypes();
        Class ret = method.getReturnType();
        boolean batch = false;
        if (JavaTypeUtils.isInt(ret)) {
            batch = false;
        } else if (ret.isArray()) {
            //如果更新语句返回了int[],
            Class type = ret.getComponentType();
            if (JavaTypeUtils.isInt(type)) {
                batch = true;
            }
        }
        //通过输入参数判断
        if (paras.length == 1) {
            Class first = paras[0];
            if (List.class.isAssignableFrom(first) && this.isUpdateBatchByFirstList(method)) {
                batch = true;
            } else if (first.isArray()) {
                Class ct = first.getComponentType();
                if (this.isPojo(ct)) {
                    batch = true;
                }
            }
        }
        if (batch) {
            if (isSqlId) {
                return dbClient.executeBatchTemplate(sql, (List<Map<String, Object>>) params[0]);
            } else {
                return dbClient.executeBatchTemplateSource(sql, (List<Map<String, Object>>) params[0]);
            }
        } else {
            Map<String, Object> paraMap = new LinkedHashMap<>();
            convertParam(method, params, paraMap);
            if (isSqlId) {
                return dbClient.executeTemplate(sql, paraMap);
            } else {
                return dbClient.executeTemplateSource(sql, paraMap);
            }
        }
    }

    private boolean isUpdateBatchByFirstList(Method method) {

        Type firstType = method.getGenericParameterTypes()[0];
        Class type = HandleHelp.getType(firstType);
        if (type == null) {
            //不知道List里面是什么，认为是batchUpdate,兼容以前情况
            return true;
        }
        return isPojo(type);

    }

    private boolean isPojo(Class type) {
        if (type.isPrimitive()) {
            return false;
        }
        if (Map.class.isAssignableFrom(type)) {
            return true;
        }

        String pkg = getPackageName(type);
        if (pkg.startsWith("java.") || pkg.startsWith("javax.")) {
            return false;
        } else {
            return true;
        }
    }

    public static String getPackageName(Class<?> clazz) {
        return StringUtils.substringBefore(clazz.getName(), ".");
    }

    protected Object parseSelectList(Method method, DBClient dbClient, String sql, Object[] paras, boolean isSqlId) {
        if (paras == null) {
            paras = ArrayUtils.EMPTY_OBJECT_ARRAY;
        }

        Class<?> returnType = method.getReturnType();
        Type retType = method.getGenericReturnType();
        Map<String, Object> paraMap = new LinkedHashMap<>();
        RowLimit rl = convertParam(method, paras, paraMap);
        if (returnType == Page.class) {
            Class resultType = HandleHelp.getPageType(retType, Map.class);

            if (isSqlId) {
                return dbClient.selectPageTemplate(rl.getOffset(), rl.getLimit(), resultType, sql, paraMap);
            } else {
                return dbClient.selectPageTemplateSource(rl.getOffset(), rl.getLimit(), resultType, sql, paraMap);
            }
            //else否则就默认为mapper类型
        }
        if (Map.class.isAssignableFrom(returnType)) {
            //如果定义返回结果为Map，无论是否泛型，都认为返回一个Map
            if (isSqlId) {
                return dbClient.selectOneTemplate(Map.class, sql, paraMap);
            } else {
                return dbClient.selectOneTemplateSource(Map.class, sql, paraMap);
            }
        } else if (List.class.isAssignableFrom(returnType)) {
            Class type = HandleHelp.getType(retType);
            if (type == null) {
                type = Map.class;
            }
            if (isSqlId) {
                return dbClient.selectListTemplate(type, sql, paraMap);
            } else {
                return dbClient.selectListTemplateSource(type, sql, paraMap);
            }
        }
        //更改类型为Single
        if (isSqlId) {
            return dbClient.selectOneTemplate(returnType, sql, paraMap);
        } else {
            return dbClient.selectOneTemplateSource(returnType, sql, paraMap);
        }
    }

    private RowLimit convertParam(Method method, Object[] paras, Map<String, Object> paraMap) {
        Class[] paraClass = method.getParameterTypes();
        Parameter[] parameters = method.getParameters();
        RowLimit rl = null;
        if (paras != null) {
            for (int i = 0; i < paraClass.length; i++) {
                Class<?> clz = paraClass[i];
                if (Map.class.isAssignableFrom(clz)) {
                    paraMap.putAll((Map<? extends String, ?>) paras[i]);
                } else if (clz == RowLimit.class) {
                    rl = (RowLimit) paras[i];
                } else {
                    OrmParam p = parameters[i].getAnnotation(OrmParam.class);
                    if (p != null) {
                        paraMap.put(p.value(), paras[i]);
                    } else {
                        paraMap.put(parameters[i].getName(), paras[i]);
                    }
                }
            }
        }
        return rl;
    }
}
